using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using SkinnedModel;
using Primitives3D;
using ConfigReader;
using MorphTarget;
using XNAViewerSample.Physics;

namespace XNAViewerSample
{
    public abstract class CModel
    {
        #region Fields
        protected Model model;
        protected String parent;
        protected String attach;
        protected Vector3 rotation;
        protected Vector3 translation;
        #endregion

        // Constructor
        public CModel()
        {
        }

        /// <summary>
        /// Gets/Sets model information structure
        /// </summary>
        public Model ModelInfo
        {
            get { return model; }
            set { model = value; }
        }
        /// <summary>
        /// Gets/Sets model parent
        /// </summary>
        public String Parent
        {
            set { parent = value; }
            get { return parent; }
        }

        /// <summary>
        /// Gets/Sets parent bone to attach to
        /// </summary>
        public String AttachTo
        {
            set { attach = value; }
            get { return attach; }
        }

    }

    /// <summary>
    /// Skinned Model structure
    /// </summary>
    public class CSkinnedModel : CModel
    {
        #region Fields
        private AnimationContainer container;
        // TODO: support also static model morphs; one morph target per modelMesh
        private Dictionary<String, CMorphTarget> morphTargets = new Dictionary<string,CMorphTarget>();
        private SkinnedSphere[] skinnedSpheres;
        private BoundingSphere[] boundingSpheres;
        private Dictionary<String, SoftBodyDynamics> softBodyDynamics = new Dictionary<string,SoftBodyDynamics>();
        private int[] visemeMap = new int[22];
        private Personality personality;
        #endregion

        /// <summary>
        /// 
        /// </summary>
        public Vector3 Rotation
        {
            set
            {
                rotation = value;
                this.Animation.modelTransform = Matrix.CreateFromYawPitchRoll(MathHelper.ToRadians(rotation.Y),
                                                                              MathHelper.ToRadians(rotation.X),
                                                                              MathHelper.ToRadians(rotation.Z)) *
                                                Matrix.CreateTranslation(translation);
            }
            get { return rotation; }
        }
        /// <summary>
        /// 
        /// </summary>
        public Vector3 Translation
        {
            set
            {
                translation = value;
                this.Animation.modelTransform = Matrix.CreateFromYawPitchRoll(MathHelper.ToRadians(rotation.Y),
                                                                              MathHelper.ToRadians(rotation.X),
                                                                              MathHelper.ToRadians(rotation.Z)) *
                                                Matrix.CreateTranslation(translation);
            }
            get { return translation; }
        }

        /// <summary>
        /// Gets model tag info as SkinningData
        /// </summary>
        public SkinningData TagInfo
        {
            get { return model.Tag as SkinningData; }
        }

        /// <summary>
        /// Animation container for the model
        /// </summary>
        public AnimationContainer Animation
        {
            get { return container; }
            set { container = value; }
        }

        public Dictionary<String,CMorphTarget> MorphTargets
        {
            get { return morphTargets; }
            set { morphTargets = value; }
        }

        public int[] VisemeMap
        {
            get { return visemeMap; }
            set { visemeMap = value; }
        }

        public Personality Personality
        {
            get { return personality; }
            set { personality = value; }
        }

        /// <summary>
        /// Skinned spheres for the model
        /// </summary>
        public SkinnedSphere[] SkinnedSpheres
        {
            get { return skinnedSpheres; }
            set { skinnedSpheres = value; }
        }

        /// <summary>
        /// Bounding spheres for the model
        /// </summary>
        public BoundingSphere[] BoundingSpheres
        {
            get { return boundingSpheres; }
            set { boundingSpheres = value; }
        }

        //public Matrix modelTransform
        //{
        //    set { this.Animation.modelTransform = value; }
        //    get { return this.Animation.modelTransform; }
        //}

        public Dictionary<String, SoftBodyDynamics> SoftBodyDynamics
        {
            set { softBodyDynamics = value; }
            get { return softBodyDynamics; }
        }

        public void AddSoftBodyDynamics(ConfigReader.Physics data)
        {
            if (softBodyDynamics == null)
                softBodyDynamics = new Dictionary<string, SoftBodyDynamics>();
            SoftBodyDynamics sbd = new SoftBodyDynamics(this);
            sbd.Type = data.Type;

            if (data.Type == ConfigReader.Physics.PType.HAIR)
            {
                const float stiffness = 10.0f;
                const float damping = 1.0f;

                // for hair types, create a series of attached springs,
                // starting from the first bone to the last
                // the first bone will be point constrained and follows model transforms
                // all bones are of type ACTIVE

                // first get default bone transforms in world space
                Matrix[] bones = new Matrix[this.TagInfo.BindPose.Count];
                Matrix[] world = new Matrix[this.TagInfo.BindPose.Count];
                this.TagInfo.BindPose.CopyTo(bones);
                world[0] = bones[0];
                for (int i = 1; i < world.Length; i++)
                {
                    world[i] = bones[i] * world[this.TagInfo.SkeletonHierarchy[i]];
                }

                float delta = 0.5f / (data.Bones.Count - 1);
                for (int i = 0; i < data.Bones.Count; i++)
                {
                    Matrix bone = world[this.TagInfo.BoneIndices[data.Bones[i]]];
                    Vector3 scale, translate;
                    Quaternion rotate;
                    bone.Decompose(out scale, out rotate, out translate);
                    // try a decreasing mass: 
                    // since hairs are less likely to move at root than at tips, more mass means less acceleration
                    SoftBodyBone sbb = new SoftBodyBone(this, data.Bones[i], 
                                                              data.Mass,// * (1.0f - delta*i), 
                                                              SBObjectType.ACTIVE);
                                                              //(i==0) ? SBObjectType.PASSIVE : SBObjectType.ACTIVE);
                    sbb.CurrPosition = translate;
                    sbb.PrevPosition = translate;
                    sbb.CurrRotation = rotate;
                    sbb.CurrScale = scale;
                    sbb.bindTransform = bone;
                    sbd.AddSoftBodyObject(sbb);
                    if (i > 0)
                    {
                        // try a decreasing stiffness
                        sbd.AddSpring(stiffness/* * (1.0f - delta * 1)*/, damping, sbd.SoftBodyObjects[i - 1], sbd.SoftBodyObjects[i]);
                        sbd.Constraints.Add(new LengthConstraint((sbd.SoftBodyObjects[i - 1].CurrPosition - sbd.SoftBodyObjects[i].CurrPosition).Length(),
                                                                 sbd.SoftBodyObjects[i - 1], sbd.SoftBodyObjects[i]));
                    }
                    else
                    {
                        // this is first bone, create a point constraint
                        sbd.Constraints.Add(new PointConstraint(sbd.SoftBodyObjects[0].CurrPosition, sbd.SoftBodyObjects[0]));
                        sbd.ConstraintIterations = 10;  // for faster computations
                    }
                }

                // add cross springs (0-2, 1-3, 2-4, 3-5...)
                if (data.Bones.Count > 2)
                {
                    for (int i = 0; i < data.Bones.Count - 2; i++)
                    {
                        //sbd.AddSpring(stiffness, damping, sbd.SoftBodyObjects[i], sbd.SoftBodyObjects[i + 2]);
                        //sbd.Constraints.Add(new LengthConstraint((sbd.SoftBodyObjects[i].CurrPosition - sbd.SoftBodyObjects[i + 2].CurrPosition).Length(),
                        //                                          sbd.SoftBodyObjects[i], sbd.SoftBodyObjects[i + 2]));
                    }
                }

                // add a global force generator
                sbd.AddGlobalForceGenerator(new Gravity(new Vector3(0.0f, -9.81f * 10, 0.0f)));

                // create an integrator
                //sbd.Integrator = new ForwardEulerIntegrator();
                sbd.Integrator = new VerletNoVelocityIntegrator(0.02f);

                // finally add to dictionary
                softBodyDynamics.Add(data.ID, sbd);
            }

            if (data.Type == ConfigReader.Physics.PType.BREAST)
            {
                const float stiffness = 10.0f;
                const float damping = 1.0f;

                // for breast types, create a series of attached springs,
                // odd are for one breast, even for the other
                // the first (2) bones will be point constrained and follows model transforms
                // all bones are of type ACTIVE

                // first get default bone transforms in world space
                Matrix[] bones = new Matrix[this.TagInfo.BindPose.Count];
                Matrix[] world = new Matrix[this.TagInfo.BindPose.Count];
                this.TagInfo.BindPose.CopyTo(bones);
                world[0] = bones[0];
                for (int i = 1; i < world.Length; i++)
                {
                    world[i] = bones[i] * world[this.TagInfo.SkeletonHierarchy[i]];
                }

                // create SoftBody bones
                for (int i = 0; i < data.Bones.Count; i++)
                {
                    Matrix bone = world[this.TagInfo.BoneIndices[data.Bones[i]]];
                    Vector3 scale, translate;
                    Quaternion rotate;
                    bone.Decompose(out scale, out rotate, out translate);
                    SoftBodyBone sbb = new SoftBodyBone(this, data.Bones[i],
                                                              data.Mass,
                                                              SBObjectType.ACTIVE);
                    sbb.CurrPosition = translate;
                    sbb.PrevPosition = translate;
                    sbb.CurrRotation = rotate;
                    sbb.CurrScale = scale;
                    sbb.bindTransform = bone;
                    sbd.AddSoftBodyObject(sbb);
                }

                // add two more softbodies to serve as anchors
                for (int i = data.Bones.Count - 2; i < data.Bones.Count; i++)
                {
                    Matrix bone = world[this.TagInfo.BoneIndices[data.Bones[i]]];
                    Vector3 scale, translate;
                    Quaternion rotate;
                    bone.Decompose(out scale, out rotate, out translate);
                    SoftBodyBone sbb = new SoftBodyBone(this, data.Bones[i],
                                                              data.Mass,
                                                              SBObjectType.PASSIVE);
                    sbb.CurrPosition = translate;
                    sbb.PrevPosition = translate;
                    sbb.CurrRotation = rotate;
                    sbb.CurrScale = scale;
                    sbb.bindTransform = bone;
                    sbd.AddSoftBodyObject(sbb);
                }

                // attach lateral springs
                for (int i=0; i<data.Bones.Count/2; i++) 
                {
                    if (i > 0)
                    {
                        sbd.AddSpring(stiffness, damping, sbd.SoftBodyObjects[2*i - 2], sbd.SoftBodyObjects[2*i]);
                        sbd.Constraints.Add(new LengthConstraint((sbd.SoftBodyObjects[2*i - 2].CurrPosition - sbd.SoftBodyObjects[2*i].CurrPosition).Length(),
                                                                  sbd.SoftBodyObjects[2*i - 2], sbd.SoftBodyObjects[2*i]));
                        sbd.AddSpring(stiffness, damping, sbd.SoftBodyObjects[2*i - 1], sbd.SoftBodyObjects[2*i + 1]);
                        sbd.Constraints.Add(new LengthConstraint((sbd.SoftBodyObjects[2*i - 1].CurrPosition - sbd.SoftBodyObjects[2*i + 1].CurrPosition).Length(),
                                                                  sbd.SoftBodyObjects[2*i - 1], sbd.SoftBodyObjects[2*i + 1]));
                        
                        if (i + 1 == data.Bones.Count/2)
                        {
                            // create spring from first to last bone, to keep breast from collapsing
                            sbd.AddSpring(stiffness, damping, sbd.SoftBodyObjects[0], sbd.SoftBodyObjects[2 * i]);
                            sbd.AddSpring(stiffness, damping, sbd.SoftBodyObjects[1], sbd.SoftBodyObjects[2 * i + 1]);
                            // add length constraint to maintain bone form
                            sbd.Constraints.Add(new LengthConstraint((sbd.SoftBodyObjects[0].CurrPosition - sbd.SoftBodyObjects[2*i].CurrPosition).Length(),
                                                                      sbd.SoftBodyObjects[0], sbd.SoftBodyObjects[2*i]));
                            sbd.Constraints.Add(new LengthConstraint((sbd.SoftBodyObjects[1].CurrPosition - sbd.SoftBodyObjects[2 * i + 1].CurrPosition).Length(),
                                                                      sbd.SoftBodyObjects[1], sbd.SoftBodyObjects[2 * i + 1]));
                        }
                    }
                    else
                    {
                        // this is first bone, create a point constraint
                        sbd.Constraints.Add(new PointConstraint(sbd.SoftBodyObjects[i].CurrPosition, sbd.SoftBodyObjects[i]));
                        sbd.Constraints.Add(new PointConstraint(sbd.SoftBodyObjects[i + 1].CurrPosition, sbd.SoftBodyObjects[i + 1]));
                        sbd.ConstraintIterations = 10;  // for faster computations
                    }                   
                }

                // attach to anchors
                for (int i = data.Bones.Count - 2; i < data.Bones.Count; i++)
                {
                    sbd.AddSpring(100.0f*stiffness, 50.0f*damping, sbd.SoftBodyObjects[i], sbd.SoftBodyObjects[i+2]);
                }
                               
                // add a global force generator
                sbd.AddGlobalForceGenerator(new Gravity(new Vector3(0.0f, -9.81f, 0.0f)));

                // create an integrator
                //sbd.Integrator = new ForwardEulerIntegrator();
                sbd.Integrator = new VerletNoVelocityIntegrator(0.1f);

                // finally add to dictionary
                softBodyDynamics.Add(data.ID, sbd);
            }
        }
    }

    /// <summary>
    /// Static Model structure
    /// </summary>
    public class CStaticModel : CModel
    {
        #region Fields
        public Matrix modelTransform;
        #endregion

        #region Public Methods
        /// <summary>
        /// 
        /// </summary>
        public Vector3 Rotation
        {
            set 
            { 
                rotation = value;
                this.modelTransform = Matrix.CreateFromYawPitchRoll(MathHelper.ToRadians(rotation.Y),
                                                                    MathHelper.ToRadians(rotation.X),
                                                                    MathHelper.ToRadians(rotation.Z)) *
                                      Matrix.CreateTranslation(translation);
            }
            get { return rotation; }
        }
        /// <summary>
        /// 
        /// </summary>
        public Vector3 Translation
        {
            set 
            { 
                translation = value;
                this.modelTransform = Matrix.CreateFromYawPitchRoll(MathHelper.ToRadians(rotation.Y),
                                                                    MathHelper.ToRadians(rotation.X),
                                                                    MathHelper.ToRadians(rotation.Z)) *
                                      Matrix.CreateTranslation(translation);
            }
            get { return translation; }
        }
        #endregion
    }

    /// <summary>
    /// Mesh structure
    /// </summary>
    public class CMesh
    {
        #region Fields
        private bool visible;
        private Texture2D texture;
        private Texture2D oldtexture;
        private float? shininess;
        private float? specularPower;
        private Int16 drawOrder;
        #endregion

        /// <summary>
        /// Gets/Sets mesh visibility property
        /// </summary>
        public bool IsVisible
        {
            get { return visible; }
            set { visible = value; }
        }

        /// <summary>
        /// Gets/Sets a custom texture
        /// </summary>
        public Texture2D CustomTexture
        {
            set { texture = value; }
            get { return texture; }
        }
        public Texture2D DefTexture
        {
            set { oldtexture = value; }
            get { return oldtexture; }
        }

        /// <summary>
        /// Gets/Sets a custom effect parameter
        /// </summary>
        public float? Shininess
        {
            get { return shininess; }
            set { shininess = value; }
        }

        /// <summary>
        /// Gets/Sets a custom effect parameter
        /// </summary>
        public float? SpecularPower
        {
            get { return specularPower; }
            set { specularPower = value; }
        }

        /// <summary>
        /// Gets/Sets draw order flag
        /// Default is 0; drawn according to load order
        /// If non-zero, drawn according to ascending order
        /// </summary>
        public Int16 DrawOrder
        {
            set { drawOrder = value; }
            get { return drawOrder; }
        }
    }
}